home *** CD-ROM | disk | FTP | other *** search
/ SGI Hot Mix 17 / Hot Mix 17.iso / HM17_SGI / research / lib / cw_arcball.pro < prev    next >
Text File  |  1997-07-08  |  14KB  |  425 lines

  1. ; $Id: cw_arcball.pro,v 1.10 1997/01/15 03:11:50 ali Exp $
  2. ;
  3. ; Copyright (c) 1993-1997, Research Systems, Inc.  All rights reserved.
  4. ;    Unauthorized reproduction prohibited.
  5. ;+
  6. ; NAME:
  7. ;    CW_ARCBALL
  8. ;
  9. ; PURPOSE:
  10. ;    CW_ARCBALL is a compound widget for intuitively specifying
  11. ;    three-dimensional orientations.
  12. ;
  13. ; CATEGORY:
  14. ;    Widget, 3d graphics
  15. ;
  16. ; CALLING SEQUENCE:
  17. ;    Widget_id = CW_ARCBALL(Parent)
  18. ;
  19. ; INPUTS:
  20. ;       PARENT:    The ID of the parent widget.
  21. ;
  22. ; KEYWORD PARAMETERS:
  23. ;    FRAME:    If set, draws a frame around the widget.
  24. ;        The default is FRAME=0.
  25. ;    LABEL:    A string containing the widget's label.
  26. ;    VALUE:    An initial value for the 3 x 3 rotation matrix. This
  27. ;        must be a valid rotation matrix (no translation or
  28. ;        perspective) where: transpose(value) = inverse(value).
  29. ;        This can be the upper-left corner of !P.T after executing
  30. ;        the command T3D, /RESET, ROTATE=[x,y,z]. The default
  31. ;        is the identity matrix.
  32. ;    UVALUE:    The initial user value for the widget.
  33. ;    SIZE:    The size of the square drawable area containing the arcball,
  34. ;        in pixels.  Default size = 192.    
  35. ;    UPDATE:    If set, the widget will send an event each time
  36. ;        the mouse button is released after a drag operation.
  37. ;        Otherwise, an event is only sent when the Update
  38. ;        button is pressed.
  39. ;    COLORS:    A 6-element array containing the color indices to be used.
  40. ;          Colors(0) = View axis color
  41. ;          Colors(1) = object axis color, 
  42. ;          Colors(2) = XZ plane +Y side (body top) color, 
  43. ;          Colors(3) = YZ plane (fin) color,
  44. ;          Colors(4) = XZ plane -Y side (body bottom),
  45. ;          Colors(5) = background color.
  46. ;        Default value = [ 1,7,2,3,7,0], which yields good colors
  47. ;        with the TEK_COLOR table.
  48. ;          (white, yellow, red, green, yellow, black).
  49. ;    RETAIN: Retain parameter for window, 0 = none, 1 = server's default,
  50. ;        2 = use backing store.  default = 1.
  51. ;
  52. ; OUTPUTS:
  53. ;    The ID of the widget is returned.
  54. ;
  55. ; SIDE EFFECTS:
  56. ;    Events are generated as described above. The current graphics window
  57. ;    is changed.
  58. ;
  59. ; RESTRICTIONS:
  60. ;    This widget can generate any rotation about any axis.
  61. ;    Not all rotations are compatible with the IDL SURFACE
  62. ;    procedure, which is restricted to rotations that project the
  63. ;    object Z axis parallel to the view Y axis.
  64. ;
  65. ; PROCEDURE:
  66. ;    This widget is based on "ARCBALL: A User Interface for
  67. ;    Specifying Three-Dimensional Orientation Using a Mouse", by Ken
  68. ;    Shoemake, Computer Graphics Laboratory, University of Pennsylvania,
  69. ;    Philadelphia, PA 19104. "In Arcball, human factors and mathematical
  70. ;    fundamentals come together exceptionally well."
  71. ;
  72. ;    The user drags a simulated track-ball with the mouse to interactively
  73. ;    obtain arbitrary rotations. Sequences of rotations may be cascaded.
  74. ;    The rotations may be unconstrained (about any axis), constrained to
  75. ;    the view X, Y, or Z axes, or to the object's X, Y, or Z axis.
  76. ;
  77. ;    Use the call:
  78. ;        WIDGET_CONTROL, id, /SET_VALUE
  79. ;    to draw the arcball after the widget is initially realized.
  80. ;    Also, the SET_VALUE entry will set the widget's value to the
  81. ;    given 3x3 rotation matrix and redraw the widget.
  82. ;
  83. ;    The WIDGET_CONTROL, id, GET_VALUE=v
  84. ;    call returns the current 3x3 rotation matrix.
  85. ;    
  86. ; EXAMPLE:
  87. ;    See the procedure ARCBALL_TEST, contained in this file.
  88. ;    To test CW_ARCBALL:
  89. ;    .RUN cw_arcball
  90. ;    ARCBALL_TEST
  91. ;
  92. ; MODIFICATION HISTORY:
  93. ;    DMS, RSI, September, 1993.  Written
  94. ;    ACY, RSI, January, 1994.  Correct test on initial value.
  95. ;-
  96.  
  97.  
  98. function arcball_constrain, pt0, axis
  99. ; Project the point pt0 onto the plane perpendicular
  100. ; to axis and passing thru the center of the sphere.
  101.  
  102. proj = pt0 - total(axis * pt0) * axis
  103. norm = sqrt(total(proj^2))
  104. if norm gt 0.0 then begin
  105.     s = 1.0/norm
  106.     if proj[2] lt 0.0 then s = -s
  107.     pt = s * proj
  108. endif else if axis[2] eq 1.0 then pt = [1.0, 0., 0.] $
  109. else pt = [ -axis[1], axis[0], 0.0 ] / sqrt(total(axis[0:1]^2))
  110. return, pt
  111. end
  112.  
  113.  
  114. function quaternion_m3, q
  115. ; Given a unit quaternion, q, return its 3x3 rotation matrix.
  116. x = q[0]
  117. y = q[1]
  118. z = q[2]
  119. w = q[3]
  120. a = [[ w^2+x^2-y^2-z^2, 2*(x*y+w*z), 2*(x*z-w*y)], $
  121.      [ 2*(x*y-w*z), w^2-x^2+y^2-z^2, 2*(y*z+w*x)], $
  122.      [ 2*(x*z+w*y), 2*(y*z-w*x), w^2-x^2-y^2+z^2]]
  123. return, a
  124. end
  125.  
  126.  
  127.  
  128. PRO ARCBALL_ARC, p0, p1, cx, cy, radius, COLOR=col
  129. ; Given 2 points on the unit sphere p0(3) and p1(3), draw the great circle
  130. ; arc connecting them.  (cx, cy) = center of sphere in whatever units are
  131. ; specified via the EXTRA parameter.  Radius = radius of sphere.
  132. ;
  133. len = acos(total(p0 * p1) < 1.0)
  134. if len eq 0.0 then return
  135. n = 12                  ;# of line segments
  136. s = sin(findgen(n+1) * (len / n))  ;Sines
  137. p = fltarr(2,n+1,/nozero)
  138. c = [cx, cy]
  139. for i=0, n do p[0,i] = radius * (p0 * s[n-i] + p1 * s[i])/s[n] + c
  140. plots, p, COLOR=col, /DEVICE
  141. end
  142.  
  143.  
  144.  
  145.  
  146. PRO ARCBALL_AXIS_ARC, p, cx, cy, radius, COLOR=c
  147. ; Draw the half great circle, whose plane is perpendicular to p.
  148. ;
  149. s = sqrt(1.0 - p[2]^2 > 0.0)
  150. if s eq 0.0 then begin      ;Outline of circle
  151.     x = [1,0,-1,0,1]
  152.     y = [0,1,0,-1,0]
  153.     for i=0,3 do arcball_arc, [x[i],y[i],0.0], [x[i+1], y[i+1], 0.0], $
  154.             cx, cy, radius, COLOR=c
  155. endif else begin
  156.     p0 = [ -p[0]*p[2]/s, -p[1]*p[2]/s, s]
  157.     p1 = [ p[1]/s, -p[0]/s, 0.0]
  158.     arcball_arc, p0, p1, cx, cy, radius, COLOR=c
  159.     arcball_arc, p0, -p1, cx, cy, radius,COLOR=c
  160. endelse
  161. end
  162.  
  163.  
  164. PRO CW_ARCBALL_DRAW, state, DO_ARC = do_arc  ;Draw the arcball spaceship.
  165. ; If do_arc is set, draw the great circle between pt0 and pt1
  166.  
  167. if state.window eq 0 then begin   ;First call?
  168.     WIDGET_CONTROL, state.draw_id, GET_VALUE=v
  169.     state.window = v
  170.     ENDIF
  171.  
  172. swin = !d.window
  173. wset, state.window
  174. col = state.colors
  175. erase, col[5]
  176. wsize = state.wsize
  177. c = state.center
  178. r = state.radius
  179. plots, [0,wsize], c, /device, color=col[0]
  180. plots, c, [0,wsize], /device, color=col[0]
  181. plots, state.circle, /device, color=col[0]
  182. if keyword_set(do_arc) then begin
  183.     plots, state.pt0 * r + c, /PSYM, /DEVICE, COLOR=col[0]
  184.     arcball_arc, state.pt0, state.pt1, c, c, r, COLOR = col[2]
  185.     ENDIF
  186. rot = state.cvalue
  187.  
  188. rr = .4 * r * rot         ;*** Draw the object
  189. offset = [c, c, 0] # replicate(1,3)
  190. t0 = rr # [[0,0,1], [-0.5,0,-1], [.5,0,-1]]   ;Main triangle
  191. t1 = rr # [[0,0,1], [0,.5,-1], [0,0,-1.]]
  192.  
  193. q = crossp(t0[*,0] - t0[*,1], t0[*,2] - t0[*,1])  ;which one first?
  194. ;;; [1,7,2,3]
  195. if q[2] ge 0 then begin
  196.     polyfill, t0+offset, color=col[2], /DEVICE
  197.     polyfill, t1+offset, color=col[3], /DEVICE
  198. endif else begin
  199.     polyfill, t1+offset, color=col[3], /DEVICE
  200.     polyfill, t0+offset, color=col[4], /DEVICE
  201. endelse
  202.  
  203.  
  204. for i=0,2 do begin        ;Draw each object axis
  205.     p = fltarr(3,3,3)
  206.     p[i] = 1.0
  207.     p1 = rot # p
  208.     arcball_axis_arc, p1, c, c, r, COLOR=col[1]
  209.     endfor
  210.  
  211. wset, swin
  212. end
  213.  
  214. PRO CW_ARCBALL_HELP, top
  215. s = size(top)
  216. if s[s[0]+1] eq 8 then begin        ;Called with structure?  Quit.
  217.     WIDGET_CONTROL, top.top, /DESTROY
  218.     RETURN
  219.     END
  220.  
  221. a = widget_base(/column, title='ARCBALL Help', $
  222.                 GROUP_LEADER=top, /MODAL) ;Not structure.
  223. b = WIDGET_TEXT(a, value = [ $
  224.     'Use the mouse to drag and rotate the simulated trackball.', $
  225.     'Rotate about the view Z axis by dragging outside the circle.', $
  226.     ' ', $
  227.     'Use the Constraint button to constrain rotations about the', $
  228.     'axis closest to where the mouse is first clicked.'], $
  229.     XSIZE=72, YSIZE = 6)
  230. b = WIDGET_BUTTON(a, VALUE='Dismiss', /NO_REL)
  231. WIDGET_CONTROL, a, /REAL
  232. XMANAGER, 'Arcball Help', a, /NO_BLOCK, $
  233.     EVENT_HANDLER='CW_ARCBALL_HELP', GROUP=top
  234. end
  235.  
  236. Function CW_ARCBALL_EVENT,  event
  237.  
  238. WIDGET_CONTROL, event.id, GET_UVALUE=child    ;Widget with state
  239. WIDGET_CONTROL, child, GET_UVALUE=state, /NO_COPY 
  240.  
  241. case event.id of
  242. state.draw_id: BEGIN        ;Draw event
  243.     if (event.press or state.buttons or event.release) eq 0 then goto, done
  244.     xy = ([event.x, event.y] - state.center) / state.radius
  245.     r = total(xy^2)        ;Distance from ctr
  246.     if r gt 1.0 then pt1 = [xy/sqrt(r), 0.0] $      ;Outside circle, z = 0.
  247.     else pt1 = [xy, sqrt(1.0-r)]
  248.     c = state.constrain
  249.     if event.press ne 0 then begin
  250.     if c[0] ne 0 then begin
  251.         ident = [[1.,0.,0.], [0.,1.,0.],[0.,0.,1.]]
  252.         if c[0] eq 2 then ident = state.value # ident  ;Object axes?
  253.         jmax = -1000.
  254.         for i=0,2 do begin        ;Find closest
  255.         t = total(arcball_constrain(pt1, ident[*,i]) * pt1)
  256.         if t gt jmax then begin jmax = t & j = i & endif
  257.         endfor
  258.         state.constrain[1] = j
  259.         state.pt0 = arcball_constrain(pt1, ident[*,j])
  260.     endif else state.pt0 = pt1        ;Constrained...
  261.     state.buttons = 1
  262.     endif else if state.buttons ne 0 then begin  ;Drag event
  263.     if c[0] ne 0 then begin
  264.         ident = [[1.,0.,0.], [0.,1.,0.],[0.,0.,1.]]
  265.         if c[0] eq 2 then ident = state.value # ident  ;Object axes?
  266.         pt1 = arcball_constrain(pt1, ident[*,c[1]])    
  267.         endif
  268.     state.pt1 = pt1
  269.     pt0 = state.pt0
  270.     q = [crossp(pt0, pt1), total(pt0*pt1)]  ;Quaternion
  271.     state.cvalue = quaternion_m3(q) # state.value
  272.     CW_ARCBALL_DRAW, state, /DO_ARC
  273.     ENDIF
  274.     if event.release ne 0 then begin
  275.     state.value = state.cvalue
  276.     state.buttons = 0
  277.     if state.update_id eq 0 then goto, update  ;Auto update?
  278.     endif
  279.     ENDCASE
  280. state.constrain_id: state.constrain[0] = event.index
  281. state.help_id: cw_arcball_help, event.top
  282. state.reset_id: BEGIN
  283.     state.value =  [[1.,0.,0.], [0.,1.,0.],[0.,0.,1.]]
  284.     state.cvalue = state.value
  285.     CW_ARCBALL_DRAW, state
  286.     if state.update_id eq 0 then goto, update  ;Auto update?
  287.     ENDCASE
  288. state.update_id: BEGIN
  289.   update:
  290.     value = { id: state.base, $
  291.     top: event.top, $
  292.     handler: event.handler, $
  293.     value: state.value }
  294.     WIDGET_CONTROL, child, SET_UVALUE=state, /NO_COPY  ;Save value
  295.     return, value
  296.     ENDCASE
  297. ENDCASE
  298.  
  299. done:
  300. WIDGET_CONTROL, child, SET_UVALUE=state, /NO_COPY 
  301. return, 0
  302. end
  303.  
  304. FUNCTION CW_ARCBALL_GET, id    ;Return current 3x3 matrix
  305. child = WIDGET_INFO(id, /CHILD)
  306. WIDGET_CONTROL, child, GET_UVALUE=state, /NO_COPY
  307. value = state.value
  308. WIDGET_CONTROL, child, SET_UVALUE=state, /NO_COPY
  309. return, value
  310. end
  311.  
  312. PRO CW_ARCBALL_SET, id, value
  313. child = WIDGET_INFO(id, /CHILD)
  314. WIDGET_CONTROL, child, GET_UVALUE=state, /NO_COPY
  315. if n_elements(value) eq 9 then BEGIN
  316.     state.value = value
  317.     state.cvalue = value
  318.     ENDIF
  319. CW_ARCBALL_DRAW, state
  320. WIDGET_CONTROL, child, SET_UVALUE=state, /NO_COPY
  321. end
  322.  
  323.  
  324. FUNCTION CW_ARCBALL, parent, FRAME=frame, LABEL=label, $
  325.     VALUE=sval, UVALUE=uvalue, RETAIN=retain, $
  326.     SIZE=xsize, UPDATE = autou, COLORS = colors
  327. ;
  328.   if keyword_set(parent) eq 0 then $
  329.     parent = WIDGET_BASE(title='CW_ARCBALL')
  330.  
  331.   framet = keyword_set(frame)
  332.   base = WIDGET_BASE(parent, FUNC_GET_VALUE='CW_ARCBALL_GET', $
  333.     PRO_SET_VALUE='CW_ARCBALL_SET', EVENT_FUN='CW_ARCBALL_EVENT')
  334.   if n_elements(uvalue) gt 0 then WIDGET_CONTROL, base, SET_UVALUE=uvalue
  335.  
  336.   base1 = WIDGET_BASE(base, /column, FRAME=framet)
  337.   if n_elements(label) ne 0 then $
  338.     junk = WIDGET_LABEL(base1, value=label)
  339.  
  340.   if n_elements(xsize) eq 0 then xsize = 192
  341.   if n_elements(retain) le 0 then retain = 1
  342.   draw_id = widget_draw(base1, xsize = xsize, ysize=xsize, $
  343.     /BUTTON_EVENTS, /MOTION_EVENTS, UVALUE=base1, RETAIN = retain)
  344.   constrain_id = CW_BSELECTOR(base1, ['None', 'View Axes', 'Object Axes'], $
  345.     LABEL_LEFT = 'Constraint:', UVALUE=base1)
  346.   junk = WIDGET_BASE(base1, /row)
  347.   if keyword_set(autou) eq 0 then $
  348.       update_id = WIDGET_BUTTON(junk, VALUE='Update', /NO_REL, UVALUE=base1) $
  349.   else update_id = 0L
  350.   reset_id = WIDGET_BUTTON(junk, VALUE='Reset', /NO_REL, UVALUE=base1)
  351.   help_id = WIDGET_BUTTON(junk, VALUE='Help', /NO_REL, UVALUE=base1)
  352.  
  353.   if n_elements(sval) gt 0 then begin
  354.     s = size(sval)
  355.     if s[0] ne 2 or s[1] ne 3 or s[2] ne 3 then $
  356.         message, 'Value must be a 3x3 matrix.'
  357.     err = max(abs(sval - sval # transpose(sval) # sval))  ;Valid rot mat?
  358.     if err gt 1.0e-6 then $
  359.         message,'Value is an invalid rotation matrix'
  360.   endif else sval = [[1.,0.,0.], [0.,1.,0.],[0.,0.,1.]]
  361. ; Colors are 0: View axes, 1: object axes, 2: top color, 3: sail color, 
  362. ;    4: bottom color, 5: background color.
  363.   if n_elements(colors) ne 6 then colors = [ 1,7,2,3,7,0]
  364.  
  365.     
  366.  
  367.   np = 36               ;# of points in circle
  368.   state = { CW_ARCB_STATE, $
  369.     base: base, $        ;Main base
  370.     draw_id: draw_id, $        ;Control id's
  371.     constrain_id: constrain_id, $
  372.     update_id: update_id, $
  373.     reset_id: reset_id, $
  374.     help_id: help_id, $
  375.     wsize: xsize, $        ;Window size
  376.     radius: xsize * .42, $    ;Circle radius
  377.     center: xsize / 2., $    ;Center x & y coords
  378.     window: 0, $        ;index
  379.     circle: fltarr(2, np), $    ;XY pnts for main circle
  380.     colors: colors, $        ;fixed axes, object axes, color1, color2
  381.     value: sval, $        ;Initial Rotation matrix
  382.     cvalue: sval, $        ;Current rotation matrix
  383.     buttons: 0L, $        ;mouse buttons
  384.     pt0: fltarr(3), $        ;Beginning of drag on sphere
  385.     pt1: fltarr(3), $        ;Current drag point on sphere
  386.     constrain: [0,0]}        ;type of constraint & axis index
  387.   x = findgen(np) * ((2 * !pi) / (np-1))
  388.   y = state.radius * sin(x) + state.center
  389.   x = state.radius * cos(x) + state.center
  390.   state.circle = transpose([[x],[y]])
  391.  
  392.   WIDGET_CONTROL, base1, SET_UVALUE=state, /NO_COPY
  393.   return, base
  394. END
  395.  
  396.  
  397. ;*****************************************************************
  398. pro arcball_event, event    ;Used by ARCBALL_TEST and ARCBALL_HELP
  399. ; If a button event is sent to this procedure, its top level base
  400. ; is destroyed, otherwise the event is printed.
  401. ;
  402. if tag_names(event, /STRUCTURE_NAME) eq 'WIDGET_BUTTON' then $
  403.     widget_control, event.top, /DESTROY $
  404. else print, 'Arcball:', event.value
  405. end
  406.  
  407. PRO ARCBALL_TEST        ;Simple test program.
  408. swin = !d.window
  409. a = WIDGET_BASE(/COLUMN, TITLE='Arcball Test')
  410. ; Setting the managed attribute indicates our intention to put this app
  411. ; under the control of XMANAGER, and prevents our draw widgets from
  412. ; becoming candidates for becoming the default window on WSET, -1. XMANAGER
  413. ; sets this, but doing it here prevents our own WSETs at startup from
  414. ; having that problem.
  415. WIDGET_CONTROL, /MANAGED, a
  416.  
  417. b = CW_ARCBALL(a, LABEL='Test me', size=256, /UPDATE)
  418. c = WIDGET_BUTTON(a, value='Done')
  419. WIDGET_CONTROL, a, /REALIZE
  420. tek_color
  421. WIDGET_CONTROL, b, SET_VALUE=1    ;Draw first ball
  422. wset, swin
  423. XMANAGER, 'Arcball', a, /NO_BLOCK        ;Manage it
  424. end
  425.